package edge

import (
	"context"
	"fmt"

	"github.com/DataDog/KubeHound/pkg/kubehound/graph/adapter"
	"github.com/DataDog/KubeHound/pkg/kubehound/graph/types"
	"github.com/DataDog/KubeHound/pkg/kubehound/models/converter"
	"github.com/DataDog/KubeHound/pkg/kubehound/models/shared"
	"github.com/DataDog/KubeHound/pkg/kubehound/storage/cache"
	"github.com/DataDog/KubeHound/pkg/kubehound/storage/storedb"
	"github.com/DataDog/KubeHound/pkg/kubehound/store/collections"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo/options"
)

// UnsafeReadMountlist represents dangerous mounts that can be abused to read secrets granting execution on the host.
// Paths are normalized by K8s to remove the trailing slash.
var UnsafeReadMountlist = bson.A{
	"/",
	"/home",
	primitive.Regex{Pattern: "^/home/[a-zA-Z0-9]*/\\.ssh$"},
	"/root",
	"/root/.ssh",
	"/proc",
	"/etc",
}

func init() {
	Register(&ExploitHostRead{}, RegisterDefault)
}

type ExploitHostRead struct {
	BaseEdge
}

type exploitHostReadGroup struct {
	Volume primitive.ObjectID `bson:"_id" json:"volume"`
	Node   primitive.ObjectID `bson:"node_id" json:"node"`
}

func (e *ExploitHostRead) Label() string {
	return "EXPLOIT_HOST_READ"
}

func (e *ExploitHostRead) Name() string {
	return "ExploitHostRead"
}

func (e *ExploitHostRead) AttckTechniqueID() AttckTechniqueID {
	return AttckTechniqueEscapeToHost
}

func (e *ExploitHostRead) AttckTacticID() AttckTacticID {
	return AttckTacticPrivilegeEscalation
}

func (e *ExploitHostRead) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) {
	typed, ok := entry.(*exploitHostReadGroup)
	if !ok {
		return nil, fmt.Errorf("invalid type passed to processor: %T", entry)
	}

	return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Volume, typed.Node, map[string]any{
		"attckTechniqueID": string(e.AttckTechniqueID()),
		"attckTacticID":    string(e.AttckTacticID()),
	})
}

func (e *ExploitHostRead) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader,
	callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error {

	volumes := adapter.MongoDB(ctx, store).Collection(collections.VolumeName)

	filter := bson.M{
		"type":     shared.VolumeTypeHost,
		"readonly": true,
		"source": bson.M{
			"$in": UnsafeReadMountlist,
		},
		"runtime.runID":        e.runtime.RunID.String(),
		"runtime.cluster.name": e.runtime.Cluster.Name,
	}

	// We just need a 1:1 mapping of the node and container to create this edge
	projection := bson.M{"_id": 1, "node_id": 1}

	cur, err := volumes.Find(ctx, filter, options.Find().SetProjection(projection))
	if err != nil {
		return err
	}
	defer cur.Close(ctx)

	return adapter.MongoCursorHandler[exploitHostReadGroup](ctx, cur, callback, complete)
}
